{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div style=\"text-align: right\" align=\"right\"><i>Peter Norvig, 3 Jan 2020</i></div>\n",
    "\n",
    "# Spelling Bee Puzzle\n",
    "\n",
    "The [Jan. 3 2020 Riddler](https://fivethirtyeight.com/features/can-you-solve-the-vexing-vexillology/) is about the popular NY Times  [Spelling Bee](https://www.nytimes.com/puzzles/spelling-bee) puzzle:\n",
    "\n",
    "*In this game, seven letters are arranged in a honeycomb lattice, with one letter in the center. Here’s the lattice from Dec. 24, 2019:*\n",
    "\n",
    "<img src=\"https://fivethirtyeight.com/wp-content/uploads/2020/01/Screen-Shot-2019-12-24-at-5.46.55-PM.png?w=1136\" width=\"150\" style=\"float:left;width:150px;height:150px;\">\n",
    "\n",
    "*The goal is to identify as many words that meet the following criteria:*\n",
    "\n",
    " (1) *The word must be at least four letters long.*\n",
    " \n",
    " (2) *The word must include the central letter.*\n",
    " \n",
    " (3) *The word cannot include any letter beyond the seven given letters.*\n",
    "\n",
    "*Note that letters can be repeated. For example, the words GAME and AMALGAM are both acceptable words. Four-letter words are worth 1 point each, while five-letter words are worth 5 points, six-letter words are worth 6 points, seven-letter words are worth 7 points, etc. Words that use all of the seven letters in the honeycomb are known as “pangrams” and earn 7 bonus points (in addition to the points for the length of the word). So in the above example, MEGAPLEX is worth 15 points.*\n",
    "\n",
    "***Which seven-letter honeycomb results in the highest possible game score?*** *To be a valid choice of seven letters, no letter can be repeated, it must not contain the letter S (that would be too easy) and there must be at least one pangram.*\n",
    "\n",
    "*For consistency, please use [this word list](https://norvig.com/ngrams/enable1.txt) to check your game score.*\n",
    "\n",
    "# My Approach\n",
    "\n",
    "Since the referenced word list came from **my** web site (I didn't make up the list; it is a standard Scrabble word list that I happen to host a copy of), I felt somewhat compelled to solve this one. \n",
    "\n",
    "Other word puzzles are hard because there are so many possibilities to consider. \n",
    "But fortunately the honeycomb puzzle (unlike [Boggle](https://github.com/aimacode/aima-python/blob/master/search.py) or [Scrabble](Scrabble.ipynb)) deals with *unordered sets* of letters, not *ordered permutations* of letters. So, once we exclude the \"S\", there are only (25 choose 7) = 480,700 *sets* of seven letters to consider.  A brute force approach could evaluate all of them (probably over the course of multiple hours). \n",
    "\n",
    "Fortunately, I noticed a better trick. The rules say that every valid honeycomb must contain a pangram. Therefore, it must be the case that every valid honeycomb **is** a pangram. How many pangrams could there be in the word list&mdash;maybe 10,000?  It must be a lot less than the number of sets of 7 letters.\n",
    "\n",
    "So here's a broad sketch of my approach:\n",
    "\n",
    "- The **best honeycomb** is the one with the highest game score among all candidate honeycombs.\n",
    "- A **candidate honeycomb** is any set of 7 letters that constitute a pangram word in the word list, with any one of the 7 letters as the center.\n",
    "- A **pangram word** is a word with exactly 7 distinct letters.\n",
    "- The **game score** for a honeycomb is the sum of the word scores for all the words that the honeycomb can make.\n",
    "- The **word score** of a word is 1 for four-letter words, or else $n$ for $n$-letters plus a 7-point bonus for pangrams.\n",
    "- A honeycomb **can make** a word if all the letters in the word are in the honeycomb, and the word contains the center letter.\n",
    "- The **set of letters** in a word (or honeycomb) can be represented as a sorted string of distinct letters (e.g., the set of letters in \"AMALGAM\" is \"AGLM\"). \n",
    "- A **honeycomb** is defined by two things, the set of seven letters, and the distinguished single center letter.\n",
    "- The **word list** can ignore words that: are less than 4 letters long; have an S; or have more than 7 distinct letters.\n",
    "\n",
    "(Note: I could have used a `frozenset` to represent a set of letters, but a sorted string seemed simpler, and for debugging purposes, I'd rather be looking at  `'AEGLMPX'` than at `frozenset({'A', 'E', 'G', 'L', 'M', 'P', 'X'})`).\n",
    "\n",
    "Each of these concepts can be implemented in a couple lines of code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "def best_honeycomb(words) -> tuple: \n",
    "    \"\"\"Return (score, honeycomb) for the honeycomb with highest game score on these words.\"\"\"\n",
    "    return max((game_score(h, words), h) for h in candidate_honeycombs(words))\n",
    "\n",
    "def candidate_honeycombs(words):\n",
    "    \"\"\"The pangram lettersets, each with all 7 centers.\"\"\"\n",
    "    pangrams = {letterset(w) for w in words if is_pangram(w)}\n",
    "    return (Honeycomb(pangram, center) for pangram in pangrams for center in pangram)\n",
    "\n",
    "def is_pangram(word) -> bool: \n",
    "    \"\"\"Does a word have exactly 7 distinct letters?\"\"\"\n",
    "    return len(set(word)) == 7\n",
    "\n",
    "def game_score(honeycomb, words) -> int:\n",
    "    \"\"\"The total score for this honeycomb; the sum of the word scores.\"\"\"\n",
    "    return sum(word_score(word) for word in words if can_make(honeycomb, word))\n",
    "\n",
    "def word_score(word) -> int: \n",
    "    \"\"\"The points for this word, including bonus for pangram.\"\"\"\n",
    "    bonus = (7 if is_pangram(word) else 0)\n",
    "    return (1 if len(word) == 4 else len(word) + bonus)\n",
    "\n",
    "def can_make(honeycomb, word) -> bool:\n",
    "    \"\"\"Can the honeycomb make this word?\"\"\"\n",
    "    (letters, center) = honeycomb\n",
    "    return center in word and all(L in letters for L in word)\n",
    "\n",
    "def letterset(word) -> str:\n",
    "    \"\"\"The set of letters in a word, as a sorted string.\n",
    "    For example, letterset('GLAM') == letterset('AMALGAM') == 'AGLM'.\"\"\"\n",
    "    return ''.join(sorted(set(word)))\n",
    "\n",
    "def Honeycomb(letters, center) -> tuple: return (letters, center)\n",
    "\n",
    "def wordlist(text) -> list:\n",
    "    \"\"\"A list of all the valid whitespace-separated words in text.\"\"\"\n",
    "    return [w for w in text.upper().split() \n",
    "            if len(w) >= 4 and 'S' not in w and len(set(w)) <= 7]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Experimentation and Small Test\n",
    "\n",
    "I'll make a tiny word list and start experimenting with it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['AMALGAM', 'GAME', 'GLAM', 'MEGAPLEX', 'CACCIATORE', 'EROTICA']"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "words = wordlist('amalgam amalgamation game games gem glam megaplex cacciatore erotica I me')\n",
    "words"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that `I`, `me` and `gem` are too short, `games` has an `S` which is not allowed, and `amalgamation` has too many distinct letters (8). We're left with six valid words out of the original eleven. Here are examples of the functions in action:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'AMALGAM': 7,\n",
       " 'GAME': 1,\n",
       " 'GLAM': 1,\n",
       " 'MEGAPLEX': 15,\n",
       " 'CACCIATORE': 17,\n",
       " 'EROTICA': 14}"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "{w: word_score(w) for w in words}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'CACCIATORE', 'EROTICA', 'MEGAPLEX'}"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "{w for w in words if is_pangram(w)}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'AMALGAM': 'AGLM',\n",
       " 'GAME': 'AEGM',\n",
       " 'GLAM': 'AGLM',\n",
       " 'MEGAPLEX': 'AEGLMPX',\n",
       " 'CACCIATORE': 'ACEIORT',\n",
       " 'EROTICA': 'ACEIORT'}"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "{w: letterset(w) for w in words}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that AMALGAM and GLAM have the same letterset, as do CACCIATORE and EROTICA. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "honeycomb = Honeycomb('AEGLMPX', 'G')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'AMALGAM': 7, 'GAME': 1, 'GLAM': 1, 'MEGAPLEX': 15}"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "{w: word_score(w) for w in words if can_make(honeycomb, w)}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "24"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "game_score(honeycomb, words)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(31, ('ACEIORT', 'T'))"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "best_honeycomb(words)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**We're done!** We know how to find the best honeycomb. But so far, we've only done it  for the tiny word list. Let's look at the real word list.\n",
    "\n",
    "# The enable1 Word List\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  172820 enable1.txt\r\n"
     ]
    }
   ],
   "source": [
    "! [ -e enable1.txt ] || curl -O http://norvig.com/ngrams/enable1.txt\n",
    "! wc -w enable1.txt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "44585"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "enable1 = wordlist(open('enable1.txt').read())\n",
    "len(enable1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "14741"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pangrams = [w for w in enable1 if is_pangram(w)]\n",
    "len(pangrams)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "7986"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pangram_sets = {letterset(w) for w in pangrams}\n",
    "len(pangram_sets)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "55902"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "_ * 7"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So to recap on the number of words of various types in enable1:\n",
    "\n",
    "    172,820 total words\n",
    "     44,585 valid words (eliminating \"S\" words, short words, 8+ letter words)\n",
    "     14,741 pangram words\n",
    "      7,986 unique pangram lettersets\n",
    "     55,902 candidate honeycombs\n",
    "\n",
    "How long will it take to run `best_honeycomb(enable1)`? Let's estimate by checking how long it takes to compute the game score of a single honeycomb:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 10.5 ms, sys: 286 µs, total: 10.8 ms\n",
      "Wall time: 10.8 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "153"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time game_score(honeycomb, enable1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That's to compute one `game_score`. Multiply by 55,902 candidate honeycombs and we get somewhere in the 10 minute range. I could run `best_honeycomb(enable1)` right now and take a coffee break until it completes, but I'm predisposed to think that a puzzle like this deserves a more elegant solution. I know that [Project Euler](https://projecteuler.net/) designs their puzzles so that a good solution runs in less than a minute, so I'll make that my goal here.\n",
    "\n",
    "# Making it Faster\n",
    "\n",
    "Here's how I think about making a more efficient program:\n",
    "\n",
    "- We're doing a `game_score` for each of the 55,902  `candidate_honeycombs`. \n",
    "- `game_score` has to **look at each word in the wordlist, and test if it is a subset of the honeycomb.**\n",
    "- We can speed things up by flipping the test around: **look at each letter subset of the honeycomb, and test if it is in the word list.**\n",
    "- By **letter subset** I mean a letter set containing a subset of the letters in the honeycomb, and definitely containing the center. So, for  `Honeycomb('ACEIORT', 'T')` the letter subsets are `['T', 'AT', 'CT', 'ET', 'IT', 'OT', 'RT', 'ACT', 'AET', ...]`\n",
    "- Why will flipping the test be faster? Because there are 44,585 words in the word list and only 64 letter subsets of a honeycomb. (A subset must include the center letter, and it may or may not include each of the other 6 letters, so there are exactly $2^6 = 64$ letter subsets of each pangram.)\n",
    "- We're left with the problem of deciding if a letter subset is a word. In fact, a letter subset might correspond to multiple words (e.g. `'AGLM'` corresponds to both `GLAM` and `AMALGAM`). \n",
    "- Ultimately we're more interested in the total number of points that a letter subset corresponds to, not in the individual word(s).\n",
    "- So I will create a table of `{letter_subset: total_points}` giving the total number of word score points for all the words that correspond to the letter subset. I call this a `points_table`.\n",
    "- Since the points table is independent of any honeycomb, I can compute it once and for all; I don't need to recompute it for each honeycomb.\n",
    "- To compute `game_score`, just take the sum of the 64 letter subset entries in the points table.\n",
    "\n",
    "Here's the code. Notice I didn't want to redefine the global function `game_score` with a different signature, so instead I made it be a local function that references the local `pts_table`,"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import Counter, defaultdict\n",
    "from itertools import combinations\n",
    "\n",
    "def best_honeycomb(words) -> tuple: \n",
    "    \"\"\"Return (score, honeycomb) for the honeycomb with highest score on these words.\"\"\"\n",
    "    pts_table = points_table(words)\n",
    "    def game_score(honeycomb) -> int: \n",
    "        return sum(pts_table[s] for s in letter_subsets(honeycomb))\n",
    "    return max((game_score(h), h) for h in candidate_honeycombs(words))\n",
    "\n",
    "def points_table(words) -> dict:\n",
    "    \"\"\"Return a dict of {letterset: points} from words.\"\"\"\n",
    "    table = Counter()\n",
    "    for w in words:\n",
    "        table[letterset(w)] += word_score(w)\n",
    "    return table\n",
    "\n",
    "def letter_subsets(honeycomb) -> list:\n",
    "    \"\"\"The 64 subsets of the letters in the honeycomb (that must contain the center letter).\"\"\"\n",
    "    (letters, center) = honeycomb\n",
    "    return [''.join(subset) \n",
    "            for n in range(1, 8) \n",
    "            for subset in combinations(letters, n)\n",
    "            if center in subset]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's get a feel for how this works. First the `letter_subsets`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['C', 'AC', 'BC', 'CD', 'ABC', 'ACD', 'BCD', 'ABCD']"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# A 4-letter honeycomb makes 2**3 = 8 subsets; 7-letter honeycombs make 2**7 == 64\n",
    "letter_subsets(('ABCD', 'C')) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now the `points_table`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['AMALGAM', 'GAME', 'GLAM', 'MEGAPLEX', 'CACCIATORE', 'EROTICA']"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "words # Remind me again what the words are?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Counter({'AGLM': 8, 'AEGM': 1, 'AEGLMPX': 15, 'ACEIORT': 31})"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "points_table(words)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The letterset `'ACEIORT'` gets 31 points, 17 for CACCIATORE and 14 for EROTICA, and the letterset `'AGLM'` gets 8 points, 7 for AMALGAM and 1 for GLAM. The other lettersets represent one word each. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's test that `best_honeycomb(words)` gets the same answer as before, and that the points table has the same set of pangrams as before."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert best_honeycomb(words) == (31, ('ACEIORT', 'T'))\n",
    "assert pangram_sets == {s for s in points_table(enable1) if len(s) == 7}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# The Solution"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, the solution to the puzzle:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 1.84 s, sys: 4.03 ms, total: 1.84 s\n",
      "Wall time: 1.85 s\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(3898, ('AEGINRT', 'R'))"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%time best_honeycomb(enable1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Wow! 3898 is a high score!** And it took only 2 seconds to find it!\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Making it Even Fasterer\n",
    "\n",
    "OK, that was 30 times faster than my goal of one minute. It was a nice optimization to look at only 64 letter subsets rather than 44,585 words. But I'm still looking at 103,187 honeycombs, and I feel that some of them are a waste of time.  Consider the pangram \"JUKEBOX\". With the uncommon letters J, K, and X, it does not look like a high-scoring honeycomb, no matter what center we choose. So why waste time trying all seven centers? Here's the outline of a faster `best_honeycomb`:\n",
    "\n",
    "- Go through the pangrams as before\n",
    "- However, always keep track of the best score and the best honeycomb that we have found so far.\n",
    "- For each new pangram, first see how many  points it would score if we ignore the restrriction that a particular center letter must be used. (I compute that with `game_score('')`, where again `game_score` is a local function,\n",
    "this time with access to both `pts_table` and `subsets`.)\n",
    "- Only if `game_score('')` is better than the best score found so far, then evaluate `game_score(C)` for each of the seven possible centers `C`.\n",
    "- In the end, return the best score and the best honeycomb."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CPU times: user 439 ms, sys: 1.93 ms, total: 441 ms\n",
      "Wall time: 441 ms\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(3898, ('AEGINRT', 'R'))"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def best_honeycomb(words) -> tuple: \n",
    "    \"\"\"Return (score, honeycomb) for the honeycomb with highest score on these words.\"\"\"\n",
    "    best_score, best_honeycomb = 0, None\n",
    "    pts_table = points_table(words)\n",
    "    pangrams = (s for s in pts_table if len(s) == 7)\n",
    "    for pangram in pangrams:\n",
    "        subsets = string_subsets(pangram)\n",
    "        def game_score(center): return sum(pts_table[s] for s in subsets if center in s)\n",
    "        if game_score('') > best_score:\n",
    "            for C in pangram:\n",
    "                if game_score(C) > best_score:\n",
    "                    best_score, best_honeycomb = game_score(C), Honeycomb(pangram, C)\n",
    "    return (best_score, best_honeycomb)\n",
    "\n",
    "def string_subsets(letters) -> list:\n",
    "    \"\"\"All subsets of a string.\"\"\"\n",
    "    return [''.join(s) \n",
    "            for n in range(len(letters) + 1) \n",
    "            for s in combinations(letters, n)]\n",
    "\n",
    "%time best_honeycomb(enable1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Looking good! We get the same answer, and in about half a second, four times faster than before.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Curiosity\n",
    "\n",
    "I'm curious about a bunch of things.\n",
    "\n",
    "What's the highest-scoring individual word?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'ANTITOTALITARIAN'"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "max(enable1, key=word_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What are some of the pangrams?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['AARDWOLF',\n",
       " 'BABBLEMENT',\n",
       " 'CABEZON',\n",
       " 'COLLOGUING',\n",
       " 'DEMERGERING',\n",
       " 'ETYMOLOGY',\n",
       " 'GARROTTING',\n",
       " 'IDENTIFY',\n",
       " 'LARVICIDAL',\n",
       " 'MORTGAGEE',\n",
       " 'OVERHELD',\n",
       " 'PRAWNED',\n",
       " 'REINITIATED',\n",
       " 'TOWHEAD',\n",
       " 'UTOPIAN']"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pangrams[::1000] # Every thousandth one"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What's the breakdown of reasons why words are invalid?\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('S', 103913), ('valid', 44585), ('>7', 23400), ('<4', 922)]"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Counter('S' if 'S' in w else '<4' if len(w) < 4 else '>7' if len(set(w)) > 7 else 'valid'\n",
    "        for w in open('enable1.txt').read().upper().split()).most_common()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are more than twice as many words with an 'S' as there are valid words.\n",
    "\n",
    "About the `points_table`: How many different letter subsets are there?  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "21661"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pts = points_table(enable1)\n",
    "len(pts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That means there's about two valid words for each letterset.\n",
    "\n",
    "Which lettersets score the most? The least?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('AEGINRT', 832),\n",
       " ('ADEGINR', 486),\n",
       " ('ACILNOT', 470),\n",
       " ('ACEINRT', 465),\n",
       " ('CEINORT', 398),\n",
       " ('AEGILNT', 392),\n",
       " ('AGINORT', 380),\n",
       " ('ADEINRT', 318),\n",
       " ('CENORTU', 318),\n",
       " ('ACDEIRT', 307),\n",
       " ('AEGILNR', 304),\n",
       " ('AEILNRT', 283),\n",
       " ('AEGINR', 270),\n",
       " ('ACINORT', 266),\n",
       " ('ADENRTU', 265),\n",
       " ('EGILNRT', 259),\n",
       " ('AILNORT', 252),\n",
       " ('DEGINR', 251),\n",
       " ('AEIMNRT', 242),\n",
       " ('ACELORT', 241)]"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pts.most_common(20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('IRY', 1),\n",
       " ('AGOY', 1),\n",
       " ('GHOY', 1),\n",
       " ('GIOY', 1),\n",
       " ('EKOY', 1),\n",
       " ('ORUY', 1),\n",
       " ('EOWY', 1),\n",
       " ('ANUY', 1),\n",
       " ('AGUY', 1),\n",
       " ('ELUY', 1),\n",
       " ('ANYZ', 1),\n",
       " ('BEUZ', 1),\n",
       " ('EINZ', 1),\n",
       " ('EKRZ', 1),\n",
       " ('ILZ', 1),\n",
       " ('CIOZ', 1),\n",
       " ('KNOZ', 1),\n",
       " ('NOZ', 1),\n",
       " ('IORZ', 1),\n",
       " ('EMYZ', 1)]"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pts.most_common()[-20:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Fancy Report\n",
    "\n",
    "I'd like to see the actual words that each honeycomb can make, in addition to the total score, and I'm curious about how the words are divided up by letterset. Here's a function to provide such a report. I remembered that there is a `fill` function in Python (it is in the `textwrap` module) but this all turned out to be more complicated than I expected. I guess it is difficult to create a practical extraction and reporting tool. I feel you, Larry Wall."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "from textwrap import fill\n",
    "\n",
    "def report(words, honeycomb=None):\n",
    "    \"\"\"Print stats, words, and word scores for the given honeycomb (or \n",
    "    for the best honeycomb if no honeycomb is given) over the given word list.\"\"\"\n",
    "    optimal = (\"\" if honeycomb else \"optimal \")\n",
    "    if honeycomb is None:\n",
    "        _, honeycomb = best_honeycomb(words)\n",
    "    subsets = letter_subsets(honeycomb)\n",
    "    bins = group_by(words, letterset)\n",
    "    score = sum(word_score(w) for w in words if letterset(w) in subsets)\n",
    "    nwords = sum(len(bins[s]) for s in subsets)\n",
    "    print(f'For this list of {Ns(len(words), \"word\")}:')\n",
    "    print(f'The {optimal}honeycomb {honeycomb} forms '\n",
    "          f'{Ns(nwords, \"word\")} for {Ns(score, \"point\")}.')\n",
    "    print(f'Here are the words formed by each subset, with pangrams first:\\n')\n",
    "    for s in sorted(subsets, key=lambda s: (-len(s), s)):\n",
    "        if bins[s]:\n",
    "            pts = sum(word_score(w) for w in bins[s])\n",
    "            print(f'{s} forms {Ns(len(bins[s]), \"word\")} for {Ns(pts, \"point\")}:')\n",
    "            words = [f'{w}({word_score(w)})' for w in sorted(bins[s])]\n",
    "            print(fill(' '.join(words), width=80,\n",
    "                       initial_indent='    ', subsequent_indent='    '))\n",
    "            \n",
    "def Ns(n, thing, plural=None):\n",
    "    \"\"\"Ns(3, 'bear') => '3 bears'; Ns(1, 'world') => '1 world'\"\"\"  \n",
    "    return f\"{n:,d} {thing if n == 1 else plurtal}\"\n",
    "\n",
    "def group_by(items, key):\n",
    "    \"Group items into bins of a dict, each bin keyed by key(item).\"\n",
    "    bins = defaultdict(list)\n",
    "    for item in items:\n",
    "        bins[key(item)].append(item)\n",
    "    return bins"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "For this list of 6 words:\n",
      "The honeycomb ('AEGLMPX', 'G') forms 4 words for 24 points.\n",
      "Here are the words formed by each subset, with pangrams first:\n",
      "\n",
      "AEGLMPX forms 1 word for 15 points:\n",
      "    MEGAPLEX(15)\n",
      "AEGM forms 1 word for 1 point:\n",
      "    GAME(1)\n",
      "AGLM forms 2 words for 8 points:\n",
      "    AMALGAM(7) GLAM(1)\n"
     ]
    }
   ],
   "source": [
    "report(words, honeycomb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "For this list of 44,585 words:\n",
      "The optimal honeycomb ('AEGINRT', 'R') forms 537 words for 3,898 points.\n",
      "Here are the words formed by each subset, with pangrams first:\n",
      "\n",
      "AEGINRT forms 50 words for 832 points:\n",
      "    AERATING(15) AGGREGATING(18) ARGENTINE(16) ARGENTITE(16) ENTERTAINING(19)\n",
      "    ENTRAINING(17) ENTREATING(17) GARNIERITE(17) GARTERING(16) GENERATING(17)\n",
      "    GNATTIER(15) GRANITE(14) GRATINE(14) GRATINEE(15) GRATINEEING(18)\n",
      "    GREATENING(17) INGRATE(14) INGRATIATE(17) INTEGRATE(16) INTEGRATING(18)\n",
      "    INTENERATING(19) INTERAGE(15) INTERGANG(16) INTERREGNA(17) INTREATING(17)\n",
      "    ITERATING(16) ITINERATING(18) NATTERING(16) RATTENING(16) REAGGREGATING(20)\n",
      "    REATTAINING(18) REGENERATING(19) REGRANTING(17) REGRATING(16)\n",
      "    REINITIATING(19) REINTEGRATE(18) REINTEGRATING(20) REITERATING(18)\n",
      "    RETAGGING(16) RETAINING(16) RETARGETING(18) RETEARING(16) RETRAINING(17)\n",
      "    RETREATING(17) TANGERINE(16) TANGIER(14) TARGETING(16) TATTERING(16)\n",
      "    TEARING(14) TREATING(15)\n",
      "AEGINR forms 35 words for 270 points:\n",
      "    AGINNER(7) AGREEING(8) ANEARING(8) ANERGIA(7) ANGERING(8) ANGRIER(7)\n",
      "    ARGININE(8) EARING(6) EARNING(7) EARRING(7) ENGRAIN(7) ENGRAINING(10)\n",
      "    ENRAGING(8) GAINER(6) GANGRENING(10) GARNERING(9) GEARING(7) GRAINER(7)\n",
      "    GRAINIER(8) GRANNIE(7) GREGARINE(9) NAGGIER(7) NEARING(7) RANGIER(7)\n",
      "    REAGIN(6) REARING(7) REARRANGING(11) REEARNING(9) REENGAGING(10) REGAIN(6)\n",
      "    REGAINER(8) REGAINING(9) REGEARING(9) REGINA(6) REGINAE(7)\n",
      "AEGIRT forms 5 words for 34 points:\n",
      "    AIGRET(6) AIGRETTE(8) GAITER(6) IRRIGATE(8) TRIAGE(6)\n",
      "AEGNRT forms 13 words for 94 points:\n",
      "    ARGENT(6) GARNET(6) GENERATE(8) GRANTEE(7) GRANTER(7) GREATEN(7) NEGATER(7)\n",
      "    REAGENT(7) REGENERATE(10) REGNANT(7) REGRANT(7) TANAGER(7) TEENAGER(8)\n",
      "AEINRT forms 30 words for 232 points:\n",
      "    ARENITE(7) ATTAINER(8) ENTERTAIN(9) ENTERTAINER(11) ENTRAIN(7) ENTRAINER(9)\n",
      "    INERRANT(8) INERTIA(7) INERTIAE(8) INTENERATE(10) INTREAT(7) ITERANT(7)\n",
      "    ITINERANT(9) ITINERATE(9) NATTIER(7) NITRATE(7) RATINE(6) REATTAIN(8)\n",
      "    REINITIATE(10) RETAIN(6) RETAINER(8) RETINA(6) RETINAE(7) RETIRANT(8)\n",
      "    RETRAIN(7) TERRAIN(7) TERTIAN(7) TRAINEE(7) TRAINER(7) TRIENNIA(8)\n",
      "AGINRT forms 21 words for 167 points:\n",
      "    AIRTING(7) ATTIRING(8) GRANITA(7) GRANTING(8) GRATIN(6) GRATING(7)\n",
      "    INGRATIATING(12) INTRIGANT(9) IRRIGATING(10) IRRITATING(10) NARRATING(9)\n",
      "    NITRATING(9) RANTING(7) RATING(6) RATTING(7) TARING(6) TARRING(7) TARTING(7)\n",
      "    TITRATING(9) TRAINING(8) TRIAGING(8)\n",
      "EGINRT forms 26 words for 218 points:\n",
      "    ENGIRT(6) ENTERING(8) GETTERING(9) GITTERN(7) GREETING(8) IGNITER(7)\n",
      "    INTEGER(7) INTERNING(9) INTERRING(9) REENTERING(10) REGREETING(10)\n",
      "    REGRETTING(10) REIGNITE(8) REIGNITING(10) REINTERRING(11) RENTING(7)\n",
      "    RETINTING(9) RETIRING(8) RETTING(7) RINGENT(7) TEETERING(9) TENTERING(9)\n",
      "    TIERING(7) TITTERING(9) TREEING(7) TRIGGERING(10)\n",
      "AEGNR forms 18 words for 120 points:\n",
      "    ANGER(5) ARRANGE(7) ARRANGER(8) ENGAGER(7) ENRAGE(6) GANGER(6) GANGRENE(8)\n",
      "    GARNER(6) GENERA(6) GRANGE(6) GRANGER(7) GREENGAGE(9) NAGGER(6) RANGE(5)\n",
      "    RANGER(6) REARRANGE(9) REENGAGE(8) REGNA(5)\n",
      "AEGRT forms 19 words for 123 points:\n",
      "    AGGREGATE(9) ERGATE(6) ETAGERE(7) GARGET(6) GARRET(6) GARTER(6) GRATE(5)\n",
      "    GRATER(6) GREAT(5) GREATER(7) REAGGREGATE(11) REGATTA(7) REGRATE(7) RETAG(5)\n",
      "    RETARGET(8) TAGGER(6) TARGE(5) TARGET(6) TERGA(5)\n",
      "AEINR forms 3 words for 19 points:\n",
      "    INANER(6) NARINE(6) RAINIER(7)\n",
      "AEIRT forms 20 words for 135 points:\n",
      "    ARIETTA(7) ARIETTE(7) ARTIER(6) ATTIRE(6) ATTRITE(7) IRATE(5) IRATER(6)\n",
      "    IRRITATE(8) ITERATE(7) RATITE(6) RATTIER(7) REITERATE(9) RETIA(5)\n",
      "    RETIARII(8) TARRIER(7) TATTIER(7) TEARIER(7) TERAI(5) TERRARIA(8) TITRATE(7)\n",
      "AENRT forms 19 words for 132 points:\n",
      "    ANTEATER(8) ANTRE(5) ENTERA(6) ENTRANT(7) ENTREAT(7) ERRANT(6) NARRATE(7)\n",
      "    NARRATER(8) NATTER(6) NEATER(6) RANTER(6) RATTEEN(7) RATTEN(6) RATTENER(8)\n",
      "    REENTRANT(9) RETREATANT(10) TANNER(6) TERNATE(7) TERRANE(7)\n",
      "AGINR forms 19 words for 138 points:\n",
      "    AGRARIAN(8) AIRING(6) ANGARIA(7) ARRAIGN(7) ARRAIGNING(10) ARRANGING(9)\n",
      "    GARAGING(8) GARNI(5) GARRING(7) GNARRING(8) GRAIN(5) GRAINING(8) INGRAIN(7)\n",
      "    INGRAINING(10) RAGGING(7) RAGING(6) RAINING(7) RANGING(7) RARING(6)\n",
      "AGIRT forms 1 word for 5 points:\n",
      "    TRAGI(5)\n",
      "AGNRT forms 1 word for 5 points:\n",
      "    GRANT(5)\n",
      "AINRT forms 9 words for 64 points:\n",
      "    ANTIAIR(7) ANTIAR(6) ANTIARIN(8) INTRANT(7) IRRITANT(8) RIANT(5) TITRANT(7)\n",
      "    TRAIN(5) TRINITARIAN(11)\n",
      "EGINR forms 24 words for 186 points:\n",
      "    ENGINEER(8) ENGINEERING(11) ERRING(6) GINGER(6) GINGERING(9) GINNER(6)\n",
      "    GINNIER(7) GREEING(7) GREENIE(7) GREENIER(8) GREENING(8) GRINNER(7)\n",
      "    NIGGER(6) REENGINEER(10) REENGINEERING(13) REGREENING(10) REIGN(5)\n",
      "    REIGNING(8) REINING(7) RENEGING(8) RENIG(5) RENIGGING(9) RERIGGING(9)\n",
      "    RINGER(6)\n",
      "EGIRT forms 4 words for 27 points:\n",
      "    GRITTIER(8) TERGITE(7) TIGER(5) TRIGGER(7)\n",
      "EGNRT forms 2 words for 12 points:\n",
      "    GERENT(6) REGENT(6)\n",
      "EINRT forms 29 words for 190 points:\n",
      "    ENTIRE(6) INERT(5) INTER(5) INTERN(6) INTERNE(7) INTERNEE(8) INTERTIE(8)\n",
      "    NETTIER(7) NITER(5) NITERIE(7) NITRE(5) NITRITE(7) NITTIER(7) REINTER(7)\n",
      "    RENITENT(8) RENTIER(7) RETINE(6) RETINENE(8) RETINITE(8) RETINT(6)\n",
      "    TEENIER(7) TENTIER(7) TERRINE(7) TINIER(6) TINNER(6) TINNIER(7) TINTER(6)\n",
      "    TRIENE(6) TRINE(5)\n",
      "GINRT forms 6 words for 43 points:\n",
      "    GIRTING(7) GRITTING(8) RINGGIT(7) TIRING(6) TRIGGING(8) TRINING(7)\n",
      "AEGR forms 17 words for 84 points:\n",
      "    AGER(1) AGGER(5) AGREE(5) ARREARAGE(9) EAGER(5) EAGERER(7) EAGRE(5) EGGAR(5)\n",
      "    GAGER(5) GAGGER(6) GARAGE(6) GEAR(1) RAGE(1) RAGEE(5) RAGGEE(6) REGEAR(6)\n",
      "    REGGAE(6)\n",
      "AEIR forms 4 words for 22 points:\n",
      "    AERIE(5) AERIER(6) AIRER(5) AIRIER(6)\n",
      "AENR forms 9 words for 40 points:\n",
      "    ANEAR(5) ARENA(5) EARN(1) EARNER(6) NEAR(1) NEARER(6) RANEE(5) REEARN(6)\n",
      "    RERAN(5)\n",
      "AERT forms 24 words for 127 points:\n",
      "    AERATE(6) ARETE(5) EATER(5) ERRATA(6) RATE(1) RATER(5) RATTER(6) REATA(5)\n",
      "    RETEAR(6) RETREAT(7) RETREATER(9) TARE(1) TARRE(5) TARTER(6) TARTRATE(8)\n",
      "    TATER(5) TATTER(6) TEAR(1) TEARER(6) TERRA(5) TERRAE(6) TETRA(5) TREAT(5)\n",
      "    TREATER(7)\n",
      "AGIR forms 2 words for 6 points:\n",
      "    AGRIA(5) RAGI(1)\n",
      "AGNR forms 5 words for 13 points:\n",
      "    GNAR(1) GNARR(5) GRAN(1) GRANA(5) RANG(1)\n",
      "AGRT forms 3 words for 13 points:\n",
      "    GRAT(1) RAGTAG(6) TAGRAG(6)\n",
      "AINR forms 4 words for 8 points:\n",
      "    AIRN(1) NAIRA(5) RAIN(1) RANI(1)\n",
      "AIRT forms 5 words for 21 points:\n",
      "    AIRT(1) ATRIA(5) RIATA(5) TIARA(5) TRAIT(5)\n",
      "ANRT forms 10 words for 50 points:\n",
      "    ANTRA(5) ARRANT(6) RANT(1) RATAN(5) RATTAN(6) TANTARA(7) TANTRA(6) TARN(1)\n",
      "    TARTAN(6) TARTANA(7)\n",
      "EGIR forms 3 words for 17 points:\n",
      "    GREIGE(6) RERIG(5) RIGGER(6)\n",
      "EGNR forms 6 words for 37 points:\n",
      "    GENRE(5) GREEN(5) GREENER(7) REGREEN(7) RENEGE(6) RENEGER(7)\n",
      "EGRT forms 7 words for 45 points:\n",
      "    EGRET(5) GETTER(6) GREET(5) GREETER(7) REGREET(7) REGRET(6) REGRETTER(9)\n",
      "EINR forms 4 words for 17 points:\n",
      "    INNER(5) REIN(1) RENIN(5) RENNIN(6)\n",
      "EIRT forms 17 words for 87 points:\n",
      "    RETIE(5) RETIRE(6) RETIREE(7) RETIRER(7) RITE(1) RITTER(6) TERRIER(7)\n",
      "    TERRIT(6) TIER(1) TIRE(1) TITER(5) TITRE(5) TITTER(6) TITTERER(8) TRIER(5)\n",
      "    TRITE(5) TRITER(6)\n",
      "ENRT forms 19 words for 104 points:\n",
      "    ENTER(5) ENTERER(7) ENTREE(6) ETERNE(6) NETTER(6) REENTER(7) RENNET(6)\n",
      "    RENT(1) RENTE(5) RENTER(6) RETENE(6) TEENER(6) TENNER(6) TENTER(6) TERN(1)\n",
      "    TERNE(5) TERREEN(7) TERRENE(7) TREEN(5)\n",
      "GINR forms 9 words for 44 points:\n",
      "    GIRN(1) GIRNING(7) GRIN(1) GRINNING(8) IRING(5) RIGGING(7) RING(1)\n",
      "    RINGING(7) RINNING(7)\n",
      "GIRT forms 3 words for 3 points:\n",
      "    GIRT(1) GRIT(1) TRIG(1)\n",
      "AER forms 7 words for 25 points:\n",
      "    AREA(1) AREAE(5) ARREAR(6) RARE(1) RARER(5) REAR(1) REARER(6)\n",
      "AGR forms 2 words for 2 points:\n",
      "    AGAR(1) RAGA(1)\n",
      "AIR forms 2 words for 2 points:\n",
      "    ARIA(1) RAIA(1)\n",
      "ART forms 5 words for 24 points:\n",
      "    ATTAR(5) RATATAT(7) TART(1) TARTAR(6) TATAR(5)\n",
      "EGR forms 4 words for 15 points:\n",
      "    EGER(1) EGGER(5) GREE(1) GREEGREE(8)\n",
      "EIR forms 2 words for 11 points:\n",
      "    EERIE(5) EERIER(6)\n",
      "ENR forms 1 word for 1 point:\n",
      "    ERNE(1)\n",
      "ERT forms 7 words for 27 points:\n",
      "    RETE(1) TEETER(6) TERETE(6) TERRET(6) TETTER(6) TREE(1) TRET(1)\n",
      "GIR forms 2 words for 7 points:\n",
      "    GRIG(1) GRIGRI(6)\n"
     ]
    }
   ],
   "source": [
    "report(enable1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# S Words\n",
    "\n",
    "What if we allowed honeycombs (and words) to have an S?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "def S_words(text) -> list:\n",
    "    \"\"\"A list of all the valid space-separated words, including words with an S.\"\"\"\n",
    "    return [w for w in text.upper().split() \n",
    "            if len(w) >= 4 and len(set(w)) <= 7]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "For this list of 98,141 words:\n",
      "The optimal honeycomb ('AEINRST', 'E') forms 1,179 words for 8,681 points.\n",
      "Here are the words formed by each subset, with pangrams first:\n",
      "\n",
      "AEINRST forms 86 words for 1,381 points:\n",
      "    ANESTRI(14) ANTISERA(15) ANTISTRESS(17) ANTSIER(14) ARENITES(15)\n",
      "    ARSENITE(15) ARSENITES(16) ARTINESS(15) ARTINESSES(17) ATTAINERS(16)\n",
      "    ENTERTAINERS(19) ENTERTAINS(17) ENTRAINERS(17) ENTRAINS(15) ENTREATIES(17)\n",
      "    ERRANTRIES(17) INERTIAS(15) INSTANTER(16) INTENERATES(18) INTERSTATE(17)\n",
      "    INTERSTATES(18) INTERSTRAIN(18) INTERSTRAINS(19) INTRASTATE(17) INTREATS(15)\n",
      "    IRATENESS(16) IRATENESSES(18) ITINERANTS(17) ITINERARIES(18) ITINERATES(17)\n",
      "    NASTIER(14) NITRATES(15) RAINIEST(15) RATANIES(15) RATINES(14) REATTAINS(16)\n",
      "    REINITIATES(18) REINSTATE(16) REINSTATES(17) RESINATE(15) RESINATES(16)\n",
      "    RESISTANT(16) RESISTANTS(17) RESTRAIN(15) RESTRAINER(17) RESTRAINERS(18)\n",
      "    RESTRAINS(16) RESTRAINT(16) RESTRAINTS(17) RETAINERS(16) RETAINS(14)\n",
      "    RETINAS(14) RETIRANTS(16) RETRAINS(15) RETSINA(14) RETSINAS(15)\n",
      "    SANITARIES(17) SEATRAIN(15) SEATRAINS(16) STAINER(14) STAINERS(15)\n",
      "    STANNARIES(17) STEARIN(14) STEARINE(15) STEARINES(16) STEARINS(15)\n",
      "    STRAINER(15) STRAINERS(16) STRAITEN(15) STRAITENS(16) STRAITNESS(17)\n",
      "    STRAITNESSES(19) TANISTRIES(17) TANNERIES(16) TEARSTAIN(16) TEARSTAINS(17)\n",
      "    TENANTRIES(17) TERNARIES(16) TERRAINS(15) TERTIANS(15) TRAINEES(15)\n",
      "    TRAINERS(15) TRANSIENT(16) TRANSIENTS(17) TRISTEARIN(17) TRISTEARINS(18)\n",
      "AEINRS forms 16 words for 124 points:\n",
      "    AIRINESS(8) AIRINESSES(10) ANSERINE(8) ANSERINES(9) ARISEN(6) ARSINE(6)\n",
      "    ARSINES(7) INSANER(7) INSNARE(7) INSNARER(8) INSNARERS(9) INSNARES(8)\n",
      "    SENARII(7) SIERRAN(7) SIRENIAN(8) SIRENIANS(9)\n",
      "AEINRT forms 30 words for 232 points:\n",
      "    ARENITE(7) ATTAINER(8) ENTERTAIN(9) ENTERTAINER(11) ENTRAIN(7) ENTRAINER(9)\n",
      "    INERRANT(8) INERTIA(7) INERTIAE(8) INTENERATE(10) INTREAT(7) ITERANT(7)\n",
      "    ITINERANT(9) ITINERATE(9) NATTIER(7) NITRATE(7) RATINE(6) REATTAIN(8)\n",
      "    REINITIATE(10) RETAIN(6) RETAINER(8) RETINA(6) RETINAE(7) RETIRANT(8)\n",
      "    RETRAIN(7) TERRAIN(7) TERTIAN(7) TRAINEE(7) TRAINER(7) TRIENNIA(8)\n",
      "AEINST forms 80 words for 713 points:\n",
      "    ANISETTE(8) ANISETTES(9) ANTISENSE(9) ANTISTATE(9) ANTSIEST(8)\n",
      "    ASININITIES(11) ASSASSINATE(11) ASSASSINATES(12) ASTATINE(8) ASTATINES(9)\n",
      "    ENTASIA(7) ENTASIAS(8) ENTASIS(7) ETESIAN(7) ETESIANS(8) INANEST(7)\n",
      "    INANITIES(9) INITIATES(9) INNATENESS(10) INNATENESSES(12) INSANEST(8)\n",
      "    INSANITIES(10) INSATIATE(9) INSATIATENESS(13) INSATIATENESSES(15)\n",
      "    INSENSATE(9) INSTANTANEITIES(15) INSTANTIATE(11) INSTANTIATES(12)\n",
      "    INSTANTNESS(11) INSTANTNESSES(13) INSTATE(7) INSTATES(8) INTESTATE(9)\n",
      "    INTESTATES(10) ISATINE(7) ISATINES(8) NASTIES(7) NASTIEST(8) NASTINESS(9)\n",
      "    NASTINESSES(11) NATTIEST(8) NATTINESS(9) NATTINESSES(11) SANITATE(8)\n",
      "    SANITATES(9) SANITIES(8) SANITISE(8) SANITISES(9) SATINET(7) SATINETS(8)\n",
      "    SENTENTIA(9) SENTENTIAE(10) SESTINA(7) SESTINAS(8) STANINE(7) STANINES(8)\n",
      "    STANNITE(8) STANNITES(9) TAENIAS(7) TAENIASES(9) TAENIASIS(9) TANSIES(7)\n",
      "    TASTINESS(9) TASTINESSES(11) TATTINESS(9) TATTINESSES(11) TENIAS(6)\n",
      "    TENIASES(8) TENIASIS(8) TETANIES(8) TETANISE(8) TETANISES(9) TINEAS(6)\n",
      "    TISANE(6) TISANES(7) TITANATES(9) TITANESS(8) TITANESSES(10) TITANITES(9)\n",
      "AEIRST forms 60 words for 473 points:\n",
      "    AERIEST(7) AIREST(6) AIRIEST(7) ARIETTAS(8) ARIETTES(8) ARISTAE(7)\n",
      "    ARISTATE(8) ARTERIES(8) ARTERITIS(9) ARTIEST(7) ARTISTE(7) ARTISTES(8)\n",
      "    ARTISTRIES(10) ARTSIER(7) ARTSIEST(8) ASSISTER(8) ASSISTERS(9) ASTERIA(7)\n",
      "    ASTERIAS(8) ATRESIA(7) ATRESIAS(8) ATTIRES(7) EATERIES(8) IRATEST(7)\n",
      "    IRRITATES(9) ITERATES(8) RARITIES(8) RATITES(7) RATTIEST(8) REITERATES(10)\n",
      "    SATIRE(6) SATIRES(7) SATIRISE(8) SATIRISES(9) SERIATE(7) SERIATES(8)\n",
      "    SESTERTIA(9) STARRIER(8) STARRIEST(9) STRAITER(8) STRAITEST(9) STRIAE(6)\n",
      "    STRIATE(7) STRIATES(8) TARRIERS(8) TARRIES(7) TARRIEST(8) TARSIER(7)\n",
      "    TARSIERS(8) TASTIER(7) TEARIEST(8) TERAIS(6) TERTIARIES(10) TITRATES(8)\n",
      "    TRAITRESS(9) TRAITRESSES(11) TREATIES(8) TREATISE(8) TREATISES(9)\n",
      "    TRISTATE(8)\n",
      "AENRST forms 40 words for 336 points:\n",
      "    ANTEATERS(9) ANTRES(6) ARRESTANT(9) ARRESTANTS(10) ARSENATE(8) ARSENATES(9)\n",
      "    ASSENTER(8) ASSENTERS(9) ASTERN(6) EARNEST(7) EARNESTNESS(11)\n",
      "    EARNESTNESSES(13) EARNESTS(8) EASTERN(7) EASTERNER(9) EASTERNERS(10)\n",
      "    ENTRANTS(8) ENTREATS(8) ERRANTS(7) NARRATERS(9) NARRATES(8) NATTERS(7)\n",
      "    NEAREST(7) RANTERS(7) RATTEENS(8) RATTENERS(9) RATTENS(7) REENTRANTS(10)\n",
      "    RETREATANTS(11) SARSENET(8) SARSENETS(9) SERENATA(8) SERENATAS(9)\n",
      "    SERENATE(8) STERNA(6) TANNERS(7) TARANTASES(10) TARTNESS(8) TARTNESSES(10)\n",
      "    TERRANES(8)\n",
      "EINRST forms 70 words for 582 points:\n",
      "    ENTERITIS(9) ENTERITISES(11) ENTIRENESS(10) ENTIRENESSES(12) ENTIRES(7)\n",
      "    ENTIRETIES(10) ENTRIES(7) ESTRIN(6) ESTRINS(7) ETERNISE(8) ETERNISES(9)\n",
      "    ETERNITIES(10) INERTNESS(9) INERTNESSES(11) INERTS(6) INSERT(6) INSERTER(8)\n",
      "    INSERTERS(9) INSERTS(7) INSETTER(8) INSETTERS(9) INSISTER(8) INSISTERS(9)\n",
      "    INTENSER(8) INTEREST(8) INTERESTS(9) INTERNEES(9) INTERNES(8) INTERNIST(9)\n",
      "    INTERNISTS(10) INTERNS(7) INTERS(6) INTERTIES(9) NITERIES(8) NITERS(6)\n",
      "    NITRES(6) NITRITES(8) REENTRIES(9) REINSERT(8) REINSERTS(9) REINTERS(8)\n",
      "    RENTIERS(8) RETINENES(9) RETINES(7) RETINITES(9) RETINITIS(9) RETINTS(7)\n",
      "    SENTRIES(8) SERENITIES(10) SINISTER(8) SINISTERNESS(12) SINISTERNESSES(14)\n",
      "    SINTER(6) SINTERS(7) STERNITE(8) STERNITES(9) STINTER(7) STINTERS(8)\n",
      "    TEENSIER(8) TEENTSIER(9) TERRINES(8) TINNERS(7) TINTERS(7) TRIENES(7)\n",
      "    TRIENS(6) TRIENTES(8) TRINES(6) TRINITIES(9) TRITENESS(9) TRITENESSES(11)\n",
      "AEINR forms 3 words for 19 points:\n",
      "    INANER(6) NARINE(6) RAINIER(7)\n",
      "AEINS forms 17 words for 129 points:\n",
      "    ANISE(5) ANISES(6) ASININE(7) EASINESS(8) EASINESSES(10) INANENESS(9)\n",
      "    INANENESSES(11) INANES(6) INSANE(6) INSANENESS(10) INSANENESSES(12)\n",
      "    NANNIES(7) SANIES(6) SANSEI(6) SANSEIS(7) SIENNA(6) SIENNAS(7)\n",
      "AEINT forms 10 words for 64 points:\n",
      "    ENTIA(5) INITIATE(8) INNATE(6) TAENIA(6) TAENIAE(7) TENIA(5) TENIAE(6)\n",
      "    TINEA(5) TITANATE(8) TITANITE(8)\n",
      "AEIRS forms 17 words for 106 points:\n",
      "    AERIES(6) AIRERS(6) ARISE(5) ARISES(6) ARRISES(7) EASIER(6) RAISE(5)\n",
      "    RAISER(6) RAISERS(7) RAISES(6) RERAISE(7) RERAISES(8) SASSIER(7) SERAI(5)\n",
      "    SERAIS(6) SIERRA(6) SIERRAS(7)\n",
      "AEIRT forms 20 words for 135 points:\n",
      "    ARIETTA(7) ARIETTE(7) ARTIER(6) ATTIRE(6) ATTRITE(7) IRATE(5) IRATER(6)\n",
      "    IRRITATE(8) ITERATE(7) RATITE(6) RATTIER(7) REITERATE(9) RETIA(5)\n",
      "    RETIARII(8) TARRIER(7) TATTIER(7) TEARIER(7) TERAI(5) TERRARIA(8) TITRATE(7)\n",
      "AEIST forms 15 words for 112 points:\n",
      "    EASIEST(7) ETATIST(7) SASSIEST(8) SATIATE(7) SATIATES(8) SATIETIES(9)\n",
      "    SIESTA(6) SIESTAS(7) STEATITE(8) STEATITES(9) TASSIE(6) TASSIES(7)\n",
      "    TASTIEST(8) TATTIES(7) TATTIEST(8)\n",
      "AENRS forms 25 words for 172 points:\n",
      "    ANEARS(6) ARENAS(6) EARNERS(7) EARNS(5) ENSNARE(7) ENSNARER(8) ENSNARERS(9)\n",
      "    ENSNARES(8) NARES(5) NEARNESS(8) NEARNESSES(10) NEARS(5) RANEES(6)\n",
      "    RARENESS(8) RARENESSES(10) REEARNS(7) RENNASE(7) RENNASES(8) SANER(5)\n",
      "    SARSEN(6) SARSENS(7) SNARE(5) SNARER(6) SNARERS(7) SNARES(6)\n",
      "AENRT forms 19 words for 132 points:\n",
      "    ANTEATER(8) ANTRE(5) ENTERA(6) ENTRANT(7) ENTREAT(7) ERRANT(6) NARRATE(7)\n",
      "    NARRATER(8) NATTER(6) NEATER(6) RANTER(6) RATTEEN(7) RATTEN(6) RATTENER(8)\n",
      "    REENTRANT(9) RETREATANT(10) TANNER(6) TERNATE(7) TERRANE(7)\n",
      "AENST forms 32 words for 217 points:\n",
      "    ANATASE(7) ANATASES(8) ANENST(6) ANNATES(7) ANSATE(6) ANTENNAS(8) ANTES(5)\n",
      "    ASSENT(6) ASSENTS(7) ENATES(6) ENTASES(7) ETNAS(5) NATES(5) NEATENS(7)\n",
      "    NEATEST(7) NEATNESS(8) NEATNESSES(10) NEATS(5) SANEST(6) SATEEN(6)\n",
      "    SATEENS(7) SENATE(6) SENATES(7) SENSATE(7) SENSATES(8) SETENANT(8)\n",
      "    SETENANTS(9) STANE(5) STANES(6) TANNATES(8) TANNEST(7) TENANTS(7)\n",
      "AERST forms 85 words for 604 points:\n",
      "    AERATES(7) ARETES(6) ARREST(6) ARRESTEE(8) ARRESTEES(9) ARRESTER(8)\n",
      "    ARRESTERS(9) ARRESTS(7) ASSERT(6) ASSERTER(8) ASSERTERS(9) ASSERTS(7)\n",
      "    ASTER(5) ASTERS(6) ATTESTER(8) ATTESTERS(9) EASTER(6) EASTERS(7) EATERS(6)\n",
      "    ERRATAS(7) ESTERASE(8) ESTERASES(9) ESTREAT(7) ESTREATS(8) RAREST(6)\n",
      "    RASTER(6) RASTERS(7) RATERS(6) RATES(5) RATTERS(7) REARREST(8) REARRESTS(9)\n",
      "    REASSERT(8) REASSERTS(9) REATAS(6) RESEAT(6) RESEATS(7) RESTART(7)\n",
      "    RESTARTS(8) RESTATE(7) RESTATES(8) RETASTE(7) RETASTES(8) RETEARS(7)\n",
      "    RETREATERS(10) RETREATS(8) SEAREST(7) SEATER(6) SEATERS(7) SERRATE(7)\n",
      "    SERRATES(8) STARE(5) STARER(6) STARERS(7) STARES(6) STARETS(7) STARTER(7)\n",
      "    STARTERS(8) STATER(6) STATERS(7) STEARATE(8) STEARATES(9) STRASSES(8)\n",
      "    STRETTA(7) STRETTAS(8) TARES(5) TARRES(6) TARTEST(7) TARTRATES(9) TASTER(6)\n",
      "    TASTERS(7) TATERS(6) TATTERS(7) TEARERS(7) TEARS(5) TEASER(6) TEASERS(7)\n",
      "    TERRAS(6) TERRASES(8) TESSERA(7) TESSERAE(8) TETRAS(6) TRASSES(7)\n",
      "    TREATERS(8) TREATS(6)\n",
      "EINRS forms 29 words for 184 points:\n",
      "    EERINESS(8) EERINESSES(10) ESERINE(7) ESERINES(8) INNERS(6) NEREIS(6)\n",
      "    REINS(5) RENINS(6) RENNINS(7) RERISEN(7) RESIN(5) RESINS(6) RINSE(5)\n",
      "    RINSER(6) RINSERS(7) RINSES(6) RISEN(5) SEINER(6) SEINERS(7) SEREIN(6)\n",
      "    SEREINS(7) SERIN(5) SERINE(6) SERINES(7) SERINS(6) SINNER(6) SINNERS(7)\n",
      "    SIREN(5) SIRENS(6)\n",
      "EINRT forms 29 words for 190 points:\n",
      "    ENTIRE(6) INERT(5) INTER(5) INTERN(6) INTERNE(7) INTERNEE(8) INTERTIE(8)\n",
      "    NETTIER(7) NITER(5) NITERIE(7) NITRE(5) NITRITE(7) NITTIER(7) REINTER(7)\n",
      "    RENITENT(8) RENTIER(7) RETINE(6) RETINENE(8) RETINITE(8) RETINT(6)\n",
      "    TEENIER(7) TENTIER(7) TERRINE(7) TINIER(6) TINNER(6) TINNIER(7) TINTER(6)\n",
      "    TRIENE(6) TRINE(5)\n",
      "EINST forms 58 words for 469 points:\n",
      "    EINSTEIN(8) EINSTEINS(9) ENTITIES(8) INSENTIENT(10) INSET(5) INSETS(6)\n",
      "    INSISTENT(9) INTENSE(7) INTENSENESS(11) INTENSENESSES(13) INTENSEST(9)\n",
      "    INTENSITIES(11) INTENTNESS(10) INTENTNESSES(12) INTENTS(7) INTESTINE(9)\n",
      "    INTESTINES(10) INTINES(7) NEIST(5) NETTIEST(8) NINETEENS(9) NINETIES(8)\n",
      "    NITES(5) NITTIEST(8) SENITI(6) SENNIT(6) SENNITS(7) SENSITISE(9)\n",
      "    SENSITISES(10) SENTI(5) SENTIENT(8) SENTIENTS(9) SESTINE(7) SESTINES(8)\n",
      "    SIENITE(7) SIENITES(8) SITTEN(6) STEIN(5) STEINS(6) TEENIEST(8) TEENSIEST(9)\n",
      "    TEENTSIEST(10) TENNIES(7) TENNIS(6) TENNISES(8) TENNIST(7) TENNISTS(8)\n",
      "    TENSITIES(9) TENTIEST(8) TESTINESS(9) TESTINESSES(11) TINES(5) TINIEST(7)\n",
      "    TININESS(8) TININESSES(10) TINNIEST(8) TINNINESS(9) TINNINESSES(11)\n",
      "EIRST forms 38 words for 262 points:\n",
      "    EERIEST(7) IRITISES(8) RESIST(6) RESISTER(8) RESISTERS(9) RESISTS(7)\n",
      "    RESITE(6) RESITES(7) RETIES(6) RETIREES(8) RETIRERS(8) RETIRES(7) RETRIES(7)\n",
      "    RITES(5) RITTERS(7) SISTER(6) SISTERS(7) SITTER(6) SITTERS(7) STIRRER(7)\n",
      "    STIRRERS(8) STRETTI(7) TERRIERS(8) TERRIES(7) TERRITS(7) TESTIER(7) TIERS(5)\n",
      "    TIRES(5) TITERS(6) TITRES(6) TITTERERS(9) TITTERS(7) TRESSIER(8)\n",
      "    TRESSIEST(9) TRIERS(6) TRIES(5) TRISTE(6) TRITEST(7)\n",
      "ENRST forms 35 words for 246 points:\n",
      "    ENTERERS(8) ENTERS(6) ENTREES(7) NERTS(5) NESTER(6) NESTERS(7) NETTERS(7)\n",
      "    REENTERS(8) RENEST(6) RENESTS(7) RENNETS(7) RENTERS(7) RENTES(6) RENTS(5)\n",
      "    RESENT(6) RESENTS(7) RETENES(7) SERENEST(8) STERN(5) STERNER(7) STERNEST(8)\n",
      "    STERNNESS(9) STERNNESSES(11) STERNS(6) TEENERS(7) TENNERS(7) TENSER(6)\n",
      "    TENTERS(7) TERNES(6) TERNS(5) TERREENS(8) TERRENES(8) TERSENESS(9)\n",
      "    TERSENESSES(11) TREENS(6)\n",
      "AEIN forms 2 words for 11 points:\n",
      "    INANE(5) NANNIE(6)\n",
      "AEIR forms 4 words for 22 points:\n",
      "    AERIE(5) AERIER(6) AIRER(5) AIRIER(6)\n",
      "AEIS forms 2 words for 13 points:\n",
      "    EASIES(6) SASSIES(7)\n",
      "AEIT forms 1 word for 6 points:\n",
      "    TATTIE(6)\n",
      "AENR forms 9 words for 40 points:\n",
      "    ANEAR(5) ARENA(5) EARN(1) EARNER(6) NEAR(1) NEARER(6) RANEE(5) REEARN(6)\n",
      "    RERAN(5)\n",
      "AENS forms 9 words for 46 points:\n",
      "    ANES(1) ANSAE(5) SANE(1) SANENESS(8) SANENESSES(10) SANES(5) SENNA(5)\n",
      "    SENNAS(6) SENSA(5)\n",
      "AENT forms 13 words for 63 points:\n",
      "    ANENT(5) ANTAE(5) ANTE(1) ANTENNA(7) ANTENNAE(8) ATTENT(6) EATEN(5) ENATE(5)\n",
      "    ETNA(1) NEAT(1) NEATEN(6) TANNATE(7) TENANT(6)\n",
      "AERS forms 26 words for 121 points:\n",
      "    AREAS(5) ARES(1) ARREARS(7) ARSE(1) ARSES(5) EARS(1) ERAS(1) ERASE(5)\n",
      "    ERASER(6) ERASERS(7) ERASES(6) RARES(5) RASE(1) RASER(5) RASERS(6) RASES(5)\n",
      "    REARERS(7) REARS(5) REASSESS(8) REASSESSES(10) SAREE(5) SAREES(6) SEAR(1)\n",
      "    SEARER(6) SEARS(5) SERA(1)\n",
      "AERT forms 24 words for 127 points:\n",
      "    AERATE(6) ARETE(5) EATER(5) ERRATA(6) RATE(1) RATER(5) RATTER(6) REATA(5)\n",
      "    RETEAR(6) RETREAT(7) RETREATER(9) TARE(1) TARRE(5) TARTER(6) TARTRATE(8)\n",
      "    TATER(5) TATTER(6) TEAR(1) TEARER(6) TERRA(5) TERRAE(6) TETRA(5) TREAT(5)\n",
      "    TREATER(7)\n",
      "AEST forms 35 words for 164 points:\n",
      "    ASSET(5) ASSETS(6) ATES(1) ATTEST(6) ATTESTS(7) EAST(1) EASTS(5) EATS(1)\n",
      "    ESTATE(6) ESTATES(7) ETAS(1) SATE(1) SATES(5) SEAT(1) SEATS(5) SETA(1)\n",
      "    SETAE(5) STASES(6) STATE(5) STATES(6) TASSE(5) TASSES(6) TASSET(6)\n",
      "    TASSETS(7) TASTE(5) TASTES(6) TATES(5) TEAS(1) TEASE(5) TEASES(6) TEATS(5)\n",
      "    TESTA(5) TESTAE(6) TESTATE(7) TESTATES(8)\n",
      "EINR forms 4 words for 17 points:\n",
      "    INNER(5) REIN(1) RENIN(5) RENNIN(6)\n",
      "EINS forms 10 words for 53 points:\n",
      "    NINES(5) NINNIES(7) NISEI(5) NISEIS(6) SEINE(5) SEINES(6) SEISIN(6)\n",
      "    SEISINS(7) SINE(1) SINES(5)\n",
      "EINT forms 6 words for 28 points:\n",
      "    INTENT(6) INTINE(6) NINETEEN(8) NITE(1) TENTIE(6) TINE(1)\n",
      "EIRS forms 20 words for 101 points:\n",
      "    IRES(1) IRISES(6) REIS(1) RERISE(6) RERISES(7) RISE(1) RISER(5) RISERS(6)\n",
      "    RISES(5) SEISER(6) SEISERS(7) SERIES(6) SERRIES(7) SIRE(1) SIREE(5)\n",
      "    SIREES(6) SIRES(5) SIRREE(6) SIRREES(7) SISSIER(7)\n",
      "EIRT forms 17 words for 87 points:\n",
      "    RETIE(5) RETIRE(6) RETIREE(7) RETIRER(7) RITE(1) RITTER(6) TERRIER(7)\n",
      "    TERRIT(6) TIER(1) TIRE(1) TITER(5) TITRE(5) TITTER(6) TITTERER(8) TRIER(5)\n",
      "    TRITE(5) TRITER(6)\n",
      "EIST forms 8 words for 41 points:\n",
      "    SISSIEST(8) SITE(1) SITES(5) STIES(5) TESTIEST(8) TESTIS(6) TIES(1)\n",
      "    TITTIES(7)\n",
      "ENRS forms 12 words for 80 points:\n",
      "    ERNES(5) ERNS(1) RESEEN(6) SERENE(6) SERENENESS(10) SERENENESSES(12)\n",
      "    SERENER(7) SERENES(7) SNEER(5) SNEERER(7) SNEERERS(8) SNEERS(6)\n",
      "ENRT forms 19 words for 104 points:\n",
      "    ENTER(5) ENTERER(7) ENTREE(6) ETERNE(6) NETTER(6) REENTER(7) RENNET(6)\n",
      "    RENT(1) RENTE(5) RENTER(6) RETENE(6) TEENER(6) TENNER(6) TENTER(6) TERN(1)\n",
      "    TERNE(5) TERREEN(7) TERRENE(7) TREEN(5)\n",
      "ENST forms 18 words for 94 points:\n",
      "    ENTENTES(8) NEST(1) NESTS(5) NETS(1) NETTS(5) SENNET(6) SENNETS(7) SENT(1)\n",
      "    SENTE(5) TEENS(5) TENETS(6) TENS(1) TENSE(5) TENSENESS(9) TENSENESSES(11)\n",
      "    TENSES(6) TENSEST(7) TENTS(5)\n",
      "ERST forms 44 words for 266 points:\n",
      "    ERST(1) ESTER(5) ESTERS(6) REEST(5) REESTS(6) RESET(5) RESETS(6) RESETTER(8)\n",
      "    RESETTERS(9) REST(1) RESTER(6) RESTERS(7) RESTRESS(8) RESTRESSES(10)\n",
      "    RESTS(5) RETEST(6) RETESTS(7) RETS(1) SEREST(6) SETTER(6) SETTERS(7)\n",
      "    STEER(5) STEERER(7) STEERERS(8) STEERS(6) STERE(5) STERES(6) STREET(6)\n",
      "    STREETS(7) STRESS(6) STRESSES(8) STRETTE(7) TEETERS(7) TERRETS(7) TERSE(5)\n",
      "    TERSER(6) TERSEST(7) TESTER(6) TESTERS(7) TETTERS(7) TREES(5) TRESS(5)\n",
      "    TRESSES(7) TRETS(5)\n",
      "AER forms 7 words for 25 points:\n",
      "    AREA(1) AREAE(5) ARREAR(6) RARE(1) RARER(5) REAR(1) REARER(6)\n",
      "AES forms 8 words for 33 points:\n",
      "    ASEA(1) ASSES(5) ASSESS(6) ASSESSES(8) EASE(1) EASES(5) SASSES(6) SEAS(1)\n",
      "AET forms 2 words for 2 points:\n",
      "    TATE(1) TEAT(1)\n",
      "EIN forms 1 word for 1 point:\n",
      "    NINE(1)\n",
      "EIR forms 2 words for 11 points:\n",
      "    EERIE(5) EERIER(6)\n",
      "EIS forms 7 words for 35 points:\n",
      "    ISSEI(5) ISSEIS(6) SEIS(1) SEISE(5) SEISES(6) SISES(5) SISSIES(7)\n",
      "EIT forms 1 word for 6 points:\n",
      "    TITTIE(6)\n",
      "ENR forms 1 word for 1 point:\n",
      "    ERNE(1)\n",
      "ENS forms 6 words for 20 points:\n",
      "    NESS(1) NESSES(6) SEEN(1) SENE(1) SENSE(5) SENSES(6)\n",
      "ENT forms 5 words for 15 points:\n",
      "    ENTENTE(7) NETT(1) TEEN(1) TENET(5) TENT(1)\n",
      "ERS forms 13 words for 52 points:\n",
      "    ERRS(1) ERSES(5) REES(1) RESEE(5) RESEES(6) SEER(1) SEERESS(7) SEERESSES(9)\n",
      "    SEERS(5) SERE(1) SERER(5) SERES(5) SERS(1)\n",
      "ERT forms 7 words for 27 points:\n",
      "    RETE(1) TEETER(6) TERETE(6) TERRET(6) TETTER(6) TREE(1) TRET(1)\n",
      "EST forms 18 words for 79 points:\n",
      "    SESTET(6) SESTETS(7) SETS(1) SETT(1) SETTEE(6) SETTEES(7) SETTS(5) STET(1)\n",
      "    STETS(5) TEES(1) TEST(1) TESTEE(6) TESTEES(7) TESTES(6) TESTS(5) TETS(1)\n",
      "    TSETSE(6) TSETSES(7)\n",
      "EN forms 1 word for 1 point:\n",
      "    NENE(1)\n",
      "ES forms 3 words for 7 points:\n",
      "    ESES(1) ESSES(5) SEES(1)\n"
     ]
    }
   ],
   "source": [
    "report(S_words(open('enable1.txt').read()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Pictures\n",
    "\n",
    "Here are pictures for the highest-scoring honeycombs, with and without an S:\n",
    "\n",
    "<img src=\"http://norvig.com/honeycombs.png\" width=\"350\">"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
